from pandas import DataFrame
from pandas import Series
from pandas import concat
from pandas import read_csv
from pandas import datetime
from sklearn.metrics import mean_squared_error
from sklearn.preprocessing import MinMaxScaler
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.layers import Dropout
from tensorflow.keras.layers import LSTM
from tensorflow.keras.models import save_model
from tensorflow.keras.models import load_model
from math import sqrt
from matplotlib import pyplot
import numpy


# 构建差分序列
def difference(dataset, interval=1):
    diff = list()
    for i in range(interval, len(dataset)):
        value = dataset[i] - dataset[i - interval]
        diff.append(value)
    return Series(diff)


# 将数据转换为监督型学习数据，NaN值补0
def timeseries_to_supervised(data, lag=1):
    df = DataFrame(data)
    columns = [df.shift(i) for i in range(1, lag + 1)]
    columns.append(df)
    df = concat(columns, axis=1)
    df.fillna(0, inplace=True)
    return df


def scale(train, test):
    # 创建一个缩放器
    scaler = MinMaxScaler(feature_range=(-1, 1))
    scaler = scaler.fit(train)
    print(train)
    # 将train从二维数组的格式转化为一个23*2的张量
    # train = train.reshape(train.shape[0], train.shape[1])
    # 使用缩放器将数据缩放到[-1, 1]之间
    train_scaled = scaler.transform(train)
    print(train_scaled)
    # transform test
    # test = test.reshape(test.shape[0], test.shape[1])
    test_scaled = scaler.transform(test)
    return scaler, train_scaled, test_scaled


def fit_lstm(train, batch_size, nb_epoch, neurons):
    # 将数据对中的X, y拆分开，形状为[23*1]
    X, y = train[:, 0:-1], train[:, -1]
    # 将2D数据拼接成3D数据，形状为[23*1*1]
    X = X.reshape(X.shape[0], 1, X.shape[1])
    # Sequential 序贯模型
    model = Sequential()
    # neurons是神经元个数，batch_input_shape是输入形状(样本数，时间步，每个时间步的步长)，
    # stateful是状态保留,reset_states是重置网络状态，网络状态和网络权重是两回事
    # 1.同一批数据反复训练很多次，可保留每次训练状态供下次使用
    # 2.不同批数据之间有顺序关联，可保留每次训练状态（一只股票被差分成多个批次）
    # 3.不同批次数据，数据之间没有关联，则不传递网络状态（多只不同股票之间）
    model.add(LSTM(neurons, batch_input_shape=(batch_size, X.shape[1], X.shape[2]), stateful=True))
    model.add(Dense(1))  # 输出数组为单个数字
    # model.add(Dropout(0.25))  # 随机失活
    # 定义损失函数和优化器
    # compile 训练模式
    # 损失函数(loss)：                                     优化器(optimizer)：
    # mean_squared_error : 均方误差                         adam : 优化算法
    # mean_absolute_error ： 平均绝对误差                    SGD ： 随机梯度下降
    # mean_absolute_percentage_error ：平均绝对误差百分比     RMSprop
    # mean_squared_logarithmic_error ：均方对数误差          Adagrad
    # squared_hinge                                       Adadelta
    # hinge                                               Adamax
    # categorical_hinge                                   Nadam
    # logcosh
    # categorical_crossentropy
    # sparse_categorical_crossentropy
    # binary_crossentropy
    # kullback_leibler_divergence
    # poisson
    # cosine_proximity
    model.compile(loss='mean_squared_error', optimizer='adam')
    for i in range(nb_epoch):
        # model.fit参数：
        # x：输入数据。如果模型只有一个输入，那么x的类型是numpy array，如果模型有多个输入，那么x的类型应当为list，list的元素是对应于各个输入的numpy array
        # y：标签，numpy array
        # batch_size：整数，指定进行梯度下降时每个batch包含的样本数。训练时一个batch的样本会被计算一次梯度下降，使目标函数优化一步。
        # verbose：日志显示，0为不在标准输出流输出日志信息，1为输出进度条记录，2为每个epoch输出一行记录
        # shuffle：布尔值或字符串，一般为布尔值，表示是否在训练过程中随机打乱输入样本的顺序。若为字符串“batch”，则是用来处理HDF5数据的特殊情况，它将在batch内部将数据打乱。
        # shuffle=False是不混淆数据顺序
        model.fit(X, y, epochs=1, batch_size=batch_size, verbose=1, shuffle=False)
        # 每训练完一个轮回，重置一次网络状态，网络状态和网络权重是两个东西
        model.reset_states()
    model.save('my_model.h5')
    return model


def forecast_lstm(model, batch_size, X):
    # 将形状为(1,)的，包含一个元素的一维数组X，构造成形状为(1,1,1)的3D张量
    X = X.reshape(1, 1, len(X))
    # 输出yhat形状为(1,1)的二维数组
    yhat = model.predict(X, batch_size=batch_size)
    # 返回二维数组中，第一行一列的yhat的数值
    return yhat[0, 0]


def invert_scale(scaler, X, y):
    # 将x，y转成一个list列表[x,y]->[0.26733207, -0.025524002]
    # [y]可以将一个数值转化成一个单元素列表
    new_row = [x for x in X] + [y]
    # new_row = [X[0]]+[y]
    # 将列表转化为一个,包含两个元素的一维数组，形状为(2,)->[0.26733207 -0.025524002]
    array = numpy.array(new_row)
    print(array.shape)
    # 将一维数组重构成形状为(1,2)的，1行、每行2个元素的，2维数组->[[ 0.26733207 -0.025524002]]
    array = array.reshape(1, len(array))
    # 逆缩放输入的形状为(1,2),输出形状为(1,2) -> [[ 73 15]]
    inverted = scaler.inverse_transform(array)
    return inverted[0, -1]


def inverse_difference(history, yhat, interval=1):
    return yhat + history[-interval]


# 加载数据
series = read_csv('data/test.csv', index_col=0, squeeze=True)
# 最后N条数据作为测试数据
testNum = 50

# 将所有数据进行差分转换
raw_values = series.values
diff_values = difference(raw_values, 1)

supervised = timeseries_to_supervised(diff_values, 1)
supervised_values = supervised.values

print(supervised_values)

train, test = supervised_values[0:-testNum], supervised_values[-testNum:]

scaler, train_scaled, test_scaled = scale(train, test)

print(test_scaled)

#单步预测，循环2000次，100个神经元，无dropout，对，我瞎调的。
lstm_model = fit_lstm(train_scaled, 1, 10, 10)
#lstm_model = load_model('my_model.h5')

# print(train_scaled)
train_reshaped = train_scaled[:, 0].reshape(len(train_scaled), 1, 1)
print(train_reshaped)

lstm_model.predict(train_reshaped, batch_size=1)

predictions = list()

predictions_error = []

for i in range(len(test_scaled)):
    # 将(testNum,2)的2D训练集test_scaled拆分成X,y；
    # 其中X是第i行的0到-1列，形状是(1,)的包含一个元素的一维数组；y是第i行，倒数第1列，是一个数值；
    X, y = test_scaled[i, 0:-1], test_scaled[i, -1]
    # 将训练好的模型lstm_model，X变量，传入预测函数，定义步长为1，
    yhat = forecast_lstm(lstm_model, 1, X)
    print(yhat)
    #print(yhat.shape)
    # 对预测出的y值逆缩放
    yhat = invert_scale(scaler, X, yhat)
    print(yhat)
    # 对预测出的y值逆差分转换
    yhat = inverse_difference(raw_values, yhat, len(test_scaled) + 1 - i)
    print(yhat)
    # 存储预测的y值
    predictions.append(yhat)
    print(yhat)
    # 获取真实的y值
    expected = raw_values[len(train) + i + 1]
    print(expected)
    predictions_error.append(expected-yhat)
    # 输出对比预测值与真实值做
    print('Month=%d, Predicted=%f, Expected=%f' % (i + 1, yhat, expected))

rmse = sqrt(mean_squared_error(raw_values[-testNum:], predictions))
print('Test RMSE: %.3f' % rmse)

print(predictions_error)
# 作图展示
pyplot.plot(raw_values[-testNum:])
pyplot.plot(predictions)
#pyplot.show()
ax = 0
pyplot.plot(raw_values[-testNum:]-predictions)
for i in range(testNum):
    ax = ax + abs(predictions[i])
bx = ax/testNum
print(bx)
pyplot.show()


